﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Forms;
using ShadeTree.Binding.Configuration;
using ShadeTree.Binding.Elements;
using ShadeTree.Binding.Lists;

namespace ShadeTree.Binding
{
    public class ScreenBinder<MODEL> : IScreenBinder
    {
        private readonly List<IScreenElement> _allElements = new List<IScreenElement>();
        protected readonly List<IBoundPart> _boundElements = new List<IBoundPart>();
        protected readonly IValidationMonitor _monitor;
        private Action _onchange = ()=>{};
        private bool _isLatched;
        private MODEL _model;


        public ScreenBinder()
        {
            _monitor = new NulloValidationMonitor();
        }

        public ScreenBinder(IValidationMonitor monitor)
        {
            _monitor = monitor;
            _monitor.Binder = this;
        }


        #region IScreenBinder Members

        public void AddElement(IScreenElement element)
        {
            _allElements.Add(element);
            var boundElement = element as IBoundPart;
            if (boundElement == null) return;

            boundElement.Binder = this;
            _boundElements.Add(boundElement);

            if (_model != null)
            {
                boundElement.Bind(_model);
            }
        }

        public void InsideLatch(Action action)
        {
            _isLatched = true;
            action();
            _isLatched = false;
        }


        public Action OnChange
        {
            set { _onchange = value; }
            get { return _onchange; }
        }

        public void MakeReadOnly(bool readOnly)
        {
            IScreenState state = readOnly ? (IScreenState) new DisableAllScreenState() : new EnableAllScreenState();
            EnableControls(state);
        }

        public bool IsLatched
        {
            get { return _isLatched; }
        }

        public void EnableControls(IScreenState state)
        {
            _isLatched = true;
            _allElements.ForEach(element => element.EnableControl(state));
            _isLatched = false;
        }

        public void MessageElements(Action<IScreenElement> action)
        {
            _allElements.ForEach(action);
        }

        public void ResetToOriginalValues()
        {
            _boundElements.ForEach(element => element.Reset());
        }

        public void UpdateScreen()
        {
            _boundElements.ForEach(element => element.Update());
            _allElements.ForEach(element => element.UpdateDisplayState(_model));
        }

        public void Validate(IBoundScreenElement element)
        {
            if (_model == null)
            {
                return;
            }

            _monitor.ValidateField(element, _model);
        }

        public void Focus(object control)
        {
            IScreenElement element = FindElementForControl(control);
            _isLatched = true;
            element.Focus();
            _isLatched = false;
        }

        public void Focus(string label)
        {
            IScreenElement element = FindElement(label);
            if (element != null)
            {
                var control = element.Control as Control;
                if (control != null)
                {
                    Focus(control);
                }
            }
        }

        public void BindToModel(object target)
        {
            BindToModel((MODEL) target);
        }

        public bool ApplyChangesToModel()
        {
            bool returnValue = true;

            _boundElements.ForEach((element) => { returnValue = returnValue && element.ApplyChanges(); });

            return returnValue;
        }

        public IScreenElement FindElementForControl(object control)
        {
            return _allElements.Find(e => e.Control == control);
        }


        public IScreenElement FindElement(string labelOrAlias)
        {
            return _allElements.Find(e => e.Matches(labelOrAlias));
        }

        public IBoundScreenElement FindElementByField(string fieldName)
        {
            return (IBoundScreenElement) _boundElements.Find(e => e.FieldName == fieldName);
        }

        public void SetDefaultValues()
        {
            _boundElements.ForEach((IBoundPart element) => element.SetDefaults());
        }

        IEnumerator<IScreenElement> IEnumerable<IScreenElement>.GetEnumerator()
        {
            return _allElements.GetEnumerator();
        }

        public IEnumerator GetEnumerator()
        {
            return ((IEnumerable<IScreenElement>) this).GetEnumerator();
        }

        #endregion

        public void Hide(Expression<Func<MODEL, object>> expression)
        {
            string fieldName = ReflectionHelper.GetProperty(expression).Name;
            FindElementByField(fieldName).Hide();
        }

        public void Show(Expression<Func<MODEL, object>> expression)
        {
            string fieldName = ReflectionHelper.GetProperty(expression).Name;
            FindElementByField(fieldName).Show();
        }

        public void CopySettings(IScreenDriver driver)
        {
            _allElements.ForEach(e => e.CopyFrom(driver));
        }

        public ScreenBinder<CHILD> AddChildBinder<CHILD>(Expression<Func<MODEL, object>> expression) where CHILD : class
        {
            PropertyInfo property = ReflectionHelper.GetProperty(expression);
            var child = new ChildScreenBinder<CHILD>(property, _monitor);
            _boundElements.Add(child);

            return (ScreenBinder<CHILD>) child.InnerBinder;
        }

        public void FillList<T>(object control, IList<T> list, Expression<Func<T, object>> expression)
            where T : IComparable
        {
            var picklist = new Picklist<T>(list);
            picklist.DisplayMember = ReflectionHelper.GetProperty(expression).Name;

            var element = (IListElement) FindElementForControl(control);
            element.FillWithList(picklist);
        }

        public void BindToModel(MODEL target)
        {
            _model = target;
            _isLatched = true;
            _boundElements.ForEach((element) => element.Bind(target));
            _allElements.ForEach(element => element.UpdateDisplayState(target));

            _isLatched = false;
        }

        public PropertyBindingExpression Bind(Expression<Func<MODEL, object>> expression)
        {
            Accessor accessor = ReflectionHelper.GetAccessor(expression);
            return new PropertyBindingExpression(this, accessor);
        }


        public void RemoveElementForControl(Control control)
        {
            IScreenElement element = FindElementForControl(control);
            if (element == null)
            {
                return;
            }

            _allElements.Remove(element);

            var boundScreenElement = element as IBoundScreenElement;
            if (boundScreenElement != null)
            {
                boundScreenElement.StopBinding();
                _boundElements.Remove(boundScreenElement);
            }
        }

        public ButtonElement BindButton(Button button)
        {
            var element = new ButtonElement(button);
            AddElement(element);

            return element;
        }

        public bool IsDirty()
        {
            if (_model == null)
            {
                return false;
            }

            foreach (IBoundScreenElement element in _boundElements)
            {
                if (element.IsDirty())
                {
                    return true;
                }
            }

            return false;
        }
    }
}